﻿#region using statements

using System.Collections.Generic;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using NGen.Managers.Logging;

#endregion

namespace NGen.Managers.GameObject
{
	/// <summary>
	/// 	Manages the creation, updating and destruction of all game objects
	/// </summary>
	public sealed class ManagerGameObjects : GameManager, IManagerGameObjects
	{
		#region local fields

		/// <summary>
		/// 	maps the objects to their id number
		/// </summary>
		private readonly Dictionary<uint, GameObject> m_dGameObjects = new Dictionary<uint, GameObject>();

		/// <summary>
		/// 	list of all the objects being destroyed this frame
		/// </summary>
		private readonly List<GameObject> m_lDestroyedObjects = new List<GameObject>();

		/// <summary>
		/// 	list of all the new objects that are being created this frame
		/// </summary>
		private readonly List<GameObject> m_lNewObjects = new List<GameObject>();

		/// <summary>
		/// 	number of the next unique object id to assign
		/// </summary>
		private uint m_nextObjectId;

		#endregion

		#region constructors

		public ManagerGameObjects() {
			Logger = GameEngine.GetService<IManagerLogs>().Log<ManagerGameObjects>();
			CurrentLoggingLevel = LoggingLevel.MINIMAL;
			SkipUpdate = false;
			SkipDraw = false;
			UpdateOrder = 200;
			DrawOrder = 200;
			m_nextObjectId = 0;
		}

		#endregion

		#region GameManager implementation

		/// <summary>
		/// 	initialize the manager
		/// </summary>
		internal override void Initialize() {
			TransformMatrix = NGen.Tools.TransformMatrix.CreateDefault();
		}

		/// <summary>
		/// 	updates all game objects within the manager, moves objects between lists
		/// </summary>
		/// <param name = "p_gameTime">the game time</param>
		internal override void Update(GameTime p_gameTime) {
			//remove all objects destroyed
			while(m_lDestroyedObjects.Count > 0) {
				GameObject.ThrowObjectDestroyedEvent(m_lDestroyedObjects[0]);
				m_lDestroyedObjects[0]._Cleanup();
				Logger.Write(Logger.BuildString("GameObject destroyed: ").Append(m_lDestroyedObjects[0].GetType().Name).Append(" ID-").Append(m_lDestroyedObjects[0].Id).ToString(), CurrentLoggingLevel);
				m_dGameObjects.Remove(m_lDestroyedObjects[0].Id);
				m_lDestroyedObjects.RemoveAt(0);
			}

			//add new objects to the list
			foreach(var gameObject in m_lNewObjects) {
				m_dGameObjects.Add(gameObject.Id, gameObject);
				Logger.Write(Logger.BuildString("GameObject added: ").Append(gameObject.GetType().Name).Append(" ID-").Append(gameObject.Id).ToString(), CurrentLoggingLevel);
			}

			m_lNewObjects.Clear();

			//update all dynamic objects
			foreach(var gameObject in m_dGameObjects.Values) {
				if(!gameObject.SkipUpdate) {
					gameObject._Update(p_gameTime);
				}
			}
		}

		/// <summary>
		/// 	draw all of the objects
		/// </summary>
		internal override void Draw(GameTime p_gameTime) {
			GameEngine.SpriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend, null, null, null, null, TransformMatrix);

			foreach(var gameObject in m_dGameObjects.Values) {
				if(!gameObject.SkipDraw) {
					gameObject._Draw(p_gameTime);
				}
			}

			GameEngine.SpriteBatch.End();
		}

		///<summary>
		///	destroy all objects
		///</summary>
		internal override void Cleanup() {
			DestroyAllGameObjects();
		}

		#endregion

		#region IManagerGameObjects methods

		///<summary>
		///	transform matrix used in the spritebatch draw call to transform all game objects
		///</summary>
		public Matrix TransformMatrix { get; set; }

		/// <summary>
		/// 	Adds the object to the manager for automatic updating and drawing
		/// </summary>
		/// <param name = "p_gameObject">the object to add</param>
		/// <returns>the unique object id</returns>
		public uint AddObject(GameObject p_gameObject) {
			//find an unused object id
			while(m_dGameObjects.ContainsKey(++m_nextObjectId)) {
				;
			}

			p_gameObject.Id = m_nextObjectId;
			m_lNewObjects.Add(p_gameObject);
			p_gameObject._Initialize();

			Logger.Write(Logger.BuildString("GameObject added:").Append(p_gameObject.GetType().Name).Append(" ID-").Append(m_nextObjectId).ToString(), CurrentLoggingLevel);
			return m_nextObjectId;
		}

		/// <summary>
		/// 	Checks to make sure an object exists
		/// </summary>
		/// <param name = "p_objectId">id number of the object to check</param>
		/// <returns>true if object exists, false otherwise </returns>
		public bool GameObjectExists(uint p_objectId) {
			if(m_dGameObjects.ContainsKey(p_objectId)) {
				return true;
			}

			foreach(var obj in m_lNewObjects) {
				if(obj.Id == p_objectId) {
					return true;
				}
			}

			return false;
		}

		/// <summary>
		/// 	Returns the object if it exists
		/// </summary>
		/// <param name = "p_objectId">the object id</param>
		/// <returns>the object whose id was passed in</returns>
		public GameObject GetGameObjectById(uint p_objectId) {
			if(GameObjectExists(p_objectId)) {
				if(m_dGameObjects.ContainsKey(p_objectId)) {
					return m_dGameObjects[p_objectId];
				}

				foreach(var obj in m_lNewObjects) {
					if(obj.Id == p_objectId) {
						return obj;
					}
				}
			}

			return null;
		}

		/// <summary>
		/// 	Returns the object if it exists, casted to the type passed in
		/// </summary>
		/// <param name = "p_objectId">the object id</param>
		/// <typeparam name = "TCastType">the type to which the object is casted</typeparam>
		/// <returns>the object whose id was passed in</returns>
		public TCastType GetGameObjectById<TCastType>(uint p_objectId) where TCastType : GameObject {
			return GetGameObjectById(p_objectId) as TCastType;
		}

		/// <summary>
		/// 	Returns a list containing all of the unique object identifiers
		/// </summary>
		/// <returns>the list of all unique object identifiers</returns>
		public List<uint> GetGameObjectIdList() {
			var list = new List<uint>(m_dGameObjects.Keys);
			foreach(var obj in m_lNewObjects) {
				list.Add(obj.Id);
			}
			return list;
		}

		/// <summary>
		/// 	Destroy all of the objects in the manager
		/// </summary>
		public void DestroyAllGameObjects() {
			foreach(var gameObject in m_dGameObjects.Values) {
				gameObject._Cleanup();
			}

			foreach(var gameObject in m_lNewObjects) {
				gameObject._Cleanup();
			}

			foreach(var gameObject in m_lDestroyedObjects) {
				gameObject._Cleanup();
			}

			Logger.Write(Logger.BuildString("All game objects destroyed. Count-").Append(m_dGameObjects.Count + m_lNewObjects.Count + m_lDestroyedObjects.Count).ToString(), CurrentLoggingLevel);

			m_dGameObjects.Clear();
			m_lNewObjects.Clear();
			m_lDestroyedObjects.Clear();
			m_nextObjectId = 0;
		}

		/// <summary>
		/// 	Destroys a specific game object
		/// </summary>
		/// <param name = "p_objectId">the id of the game object to destroy</param>
		public void DestroyGameObjectById(uint p_objectId) {
			Logger.Write(Logger.BuildString("game object destroyed:").Append(GetGameObjectById(p_objectId).GetType().Name).Append(" ID-" + p_objectId).ToString(), CurrentLoggingLevel);
			m_lDestroyedObjects.Add(GetGameObjectById(p_objectId));
		}

		#endregion
	}
}